package org.avatarqing.miclock;
import android.animation.PropertyValuesHolder;
import android.animation.ValueAnimator;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.graphics.Camera;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.DashPathEffect;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.RectF;
import android.graphics.Shader;
import android.graphics.SweepGradient;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.util.Log;
import android.util.TypedValue;
import android.view.MotionEvent;
import android.view.View;
import android.view.animation.BounceInterpolator;
import android.view.animation.LinearInterpolator;
import java.util.Calendar;
public class ClockView extends View {
private static final String TAG = ClockView.class.getSimpleName();
private static boolean DEBUG = true;
private int colorBg = 0xff237ead;
private int colorBgRing = 0x80ffffff;
private int colorMPOuterRing = Color.WHITE;
private int colorHPOuterRing = 0xccdddddd;
private int colorMinutePointer = Color.WHITE;
private int colorHourPointer = 0xccdddddd;
private int colorTriangle = 0xffdddddd;
private int colorScaleRing = 0xccdddddd;
private Paint paintBgRing;
private Paint paintProgressRing;
private Paint paintTriangle;
private Paint paintMinutePointer;
private Paint paintHourPointer;
private Paint paintMPOuterRing;
private Paint paintMPInnerCircle;
private Paint paintHPOuterRing;
private Paint paintHPInnerCircle;
private Paint paintNumber;
private Paint paintScaleRing;
private RectF boundScaleRing;
private RectF boundTimeRing;
private int centerX;
private int centerY;
private float radius;
private float radiusScaleRing;
private float radiusMPOuterRing = 20;
private float radiusMPInnerRing = 10;
private float radiusHPOuterRing = 18;
private float radiusHPInnerRing = 8;
private float strokeWidthRing = 30;
private float strokeWidthScaleRing = 2;
private float zDepthScaleRing = 180;
private float zDepthDashRing = 100;
private float zDepthHourPointer = 50;
private float zDepthMinutePointer = 0;
private float hpTopEdgeLength = 6;
private float hpBottomEdgeLength = 10;
private float hpPointerLength = 10;
private float hpTopCYOffset = 6;
private float mpTopEdgeLength = 5;
private float mpBottomEdgeLength = 8;
private float mpPointerLength = 20;
private float mpTopCYOffset = 6;
private float trianglePointerOffset = 6;
private float triangleSideLength = 40;
private float canvasRotateX = 0;
private float canvasRotateY = 0;
private float rotateHourPointer = 0;
private float rotateMinutePointer = 0;
private float rotateSecondPointer = 0;
private float scaleTextSize = 13;
private float scaleTextAngle = 5;
private float[][] scaleTextDrawCoordinates;
private float progressRingInitRotateDegree = 270;
private float canvasMaxRotateDegree = 20;
private float[] ringDashIntervals = new float[]{3f, 6f};
private float[] sweepGradientColorPos = new float[]{0f, 300f / 360f, 330f / 360f, 1f};
private int[] sweepGradientColors = new int[]{Color.TRANSPARENT, 0x80ffffff, 0xddffffff, Color.WHITE};
private ValueAnimator steadyAnim;
private ValueAnimator secondAnim;
private Path pathTriangle;
private Path pathMinutePointer;
private Path pathHourPointer;
private Matrix matrixCanvas = new Matrix();
private Matrix matrixSweepGradient = new Matrix();
private Shader sweepGradient;
private Camera camera = new Camera();
public ClockView(Context context) {
super(context);
init();
}
public ClockView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
if (DEBUG) {
Log.d(TAG, "onAttachedToWindow");
}
// startNewSecondAnim();
// register broadcast receiver
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(Intent.ACTION_TIME_TICK);
intentFilter.addAction(Intent.ACTION_TIME_CHANGED);
intentFilter.addAction(Intent.ACTION_SCREEN_ON);
intentFilter.addAction(Intent.ACTION_SCREEN_OFF);
getContext().registerReceiver(receiver, intentFilter);
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
if (DEBUG) {
Log.d(TAG, "onDetachedFromWindow");
}
getContext().unregisterReceiver(receiver);
cancelSecondAnimIfNeed();
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
reset();
}
@Override
protected void onDraw(Canvas canvas) {
canvas.drawColor(colorBg);
rotateCanvas(canvas);
drawContent(canvas);
}
private void rotateCanvas(Canvas canvas) {
matrixCanvas.reset();
camera.save();
camera.rotateX(canvasRotateX);
camera.rotateY(canvasRotateY);
camera.getMatrix(matrixCanvas);
camera.restore();
int matrixCenterX = centerX;
int matrixCenterY = centerY;
// This moves the center of the view into the upper left corner (0,0)
// which is necessary because Matrix always uses 0,0, as it's transform point
matrixCanvas.preTranslate(-matrixCenterX, -matrixCenterY);
// This happens after the camera rotations are applied, moving the view
// back to where it belongs, allowing us to rotate around the center or
// any point we choose
matrixCanvas.postTranslate(matrixCenterX, matrixCenterY);
canvas.concat(matrixCanvas);
}
private void translateCanvas(Canvas canvas, float x, float y, float z) {
matrixCanvas.reset();
camera.save();
camera.translate(x, y, z);
camera.getMatrix(matrixCanvas);
camera.restore();
int matrixCenterX = centerX;
int matrixCenterY = centerY;
matrixCanvas.preTranslate(-matrixCenterX, -matrixCenterY);
matrixCanvas.postTranslate(matrixCenterX, matrixCenterY);
canvas.concat(matrixCanvas);
}
private void drawContent(Canvas canvas) {
// Check rotate bound
if (rotateSecondPointer >= 360f) {
rotateSecondPointer %= 360f;
}
if (rotateMinutePointer >= 360f) {
rotateMinutePointer %= 360f;
}
if (rotateHourPointer >= 360f) {
rotateHourPointer %= 360f;
}
// Rotate ring sweep gradient
matrixSweepGradient.setRotate(getProgressRingRotateDegree(), centerX, centerY);
sweepGradient.setLocalMatrix(matrixSweepGradient);
paintProgressRing.setShader(sweepGradient);
// Draw scale ring
canvas.save();
translateCanvas(canvas, 0f, 0f, zDepthScaleRing);
canvas.drawArc(boundScaleRing, scaleTextAngle, 90 - 2 * scaleTextAngle, false, paintScaleRing);
canvas.drawArc(boundScaleRing, 90 + scaleTextAngle, 90 - 2 * scaleTextAngle, false, paintScaleRing);
canvas.drawArc(boundScaleRing, 180 + scaleTextAngle, 90 - 2 * scaleTextAngle, false, paintScaleRing);
canvas.drawArc(boundScaleRing, 270 + scaleTextAngle, 90 - 2 * scaleTextAngle, false, paintScaleRing);
canvas.drawText("12", scaleTextDrawCoordinates[0][0], scaleTextDrawCoordinates[0][1], paintNumber);
canvas.drawText("6", scaleTextDrawCoordinates[1][0], scaleTextDrawCoordinates[1][1], paintNumber);
canvas.drawText("9", scaleTextDrawCoordinates[2][0], scaleTextDrawCoordinates[2][1], paintNumber);
canvas.drawText("3", scaleTextDrawCoordinates[3][0], scaleTextDrawCoordinates[3][1], paintNumber);
canvas.restore();
// Draw dash ring and second pointer
canvas.save();
translateCanvas(canvas, 0f, 0f, zDepthDashRing);
canvas.drawArc(boundTimeRing, 0, 359.5f, false, paintBgRing);
canvas.drawArc(boundTimeRing, 0, 359.5f, false, paintProgressRing);
canvas.rotate(rotateSecondPointer, centerX, centerY);
canvas.drawPath(pathTriangle, paintTriangle);
canvas.restore();
// Draw hour pointer
canvas.save();
translateCanvas(canvas, 0f, 0f, zDepthHourPointer);
canvas.rotate(rotateHourPointer, centerX, centerY);
canvas.drawPath(pathHourPointer, paintHourPointer);
canvas.drawCircle(centerX, centerY, radiusHPOuterRing, paintHPOuterRing);
canvas.drawCircle(centerX, centerY, radiusHPInnerRing, paintHPInnerCircle);
canvas.restore();
// Draw minute pointer
canvas.save();
translateCanvas(canvas, 0f, 0f, zDepthMinutePointer);
canvas.rotate(rotateMinutePointer, centerX, centerY);
canvas.drawPath(pathMinutePointer, paintMinutePointer);
canvas.drawCircle(centerX, centerY, radiusMPOuterRing, paintMPOuterRing);
canvas.drawCircle(centerX, centerY, radiusMPInnerRing, paintMPInnerCircle);
canvas.restore();
}
@Override
public boolean onTouchEvent(MotionEvent event) {
float x = event.getX();
float y = event.getY();
int action = event.getActionMasked();
switch (action) {
case MotionEvent.ACTION_DOWN: {
cancelSteadyAnimIfNeed();
rotateCanvasWhenMove(x, y);
return true;
}
case MotionEvent.ACTION_MOVE: {
rotateCanvasWhenMove(x, y);
return true;
}
case MotionEvent.ACTION_UP: {
cancelSteadyAnimIfNeed();
startNewSteadyAnim();
return true;
}
}
return super.onTouchEvent(event);
}
private void rotateCanvasWhenMove(float x, float y) {
float dx = x - centerX;
float dy = y - centerY;
float percentX = dx / (getWidth() / 2);
float percentY = dy / (getHeight() / 2);
if (percentX > 1f) {
percentX = 1f;
} else if (percentX < -1f) {
percentX = -1f;
}
if (percentY > 1f) {
percentY = 1f;
} else if (percentY < -1f) {
percentY = -1f;
}
canvasRotateY = canvasMaxRotateDegree * percentX;
canvasRotateX = -(canvasMaxRotateDegree * percentY);
}
private void init() {
initValues();
startNewSecondAnim();
}
private void initValues() {
// Colors
colorBg = 0xff237ead;
colorBgRing = 0x80ffffff;
colorMPOuterRing = Color.WHITE;
colorHPOuterRing = 0xccdddddd;
colorMinutePointer = Color.WHITE;
colorHourPointer = 0xccdddddd;
colorTriangle = 0xffdddddd;
colorScaleRing = 0xccdddddd;
// Angle
progressRingInitRotateDegree = 270;
canvasMaxRotateDegree = 20;
scaleTextAngle = 5;
// Radius
radiusMPOuterRing = dp2px(8);
radiusMPInnerRing = dp2px(4);
radiusHPOuterRing = dp2px(8);
radiusHPInnerRing = dp2px(4);
// Stroke width
strokeWidthRing = dp2px(25);
strokeWidthScaleRing = dp2px(2);
// zDepth
zDepthScaleRing = dp2px(130);
zDepthDashRing = dp2px(100);
zDepthHourPointer = dp2px(50);
zDepthMinutePointer = 0;
// Text properties on scale ring
scaleTextSize = sp2px(20);
// Hour pointer
hpTopEdgeLength = dp2px(3);
hpBottomEdgeLength = dp2px(5);
hpPointerLength = radius * 3 / 5;
hpTopCYOffset = dp2px(3);
// Minute pointer
mpTopEdgeLength = dp2px(2);
mpBottomEdgeLength = dp2px(3);
mpPointerLength = radius * 4 / 5;
mpTopCYOffset = dp2px(3);
// Second pointer
trianglePointerOffset = dp2px(6);
triangleSideLength = dp2px(20);
// Ring dash intervals
ringDashIntervals = new float[]{dp2px(1), dp2px(3)};
// Sweep gradient
sweepGradientColorPos = new float[]{0f, 300f / 360f, 330f / 360f, 1f};
sweepGradientColors = new int[]{Color.TRANSPARENT, 0x80ffffff, 0xddffffff, Color.WHITE};
}
private void reset() {
initBound();
initBgRing();
initProgressRing();
initScaleRing();
initTriangleSecondPointer();
initMinutePointer();
initHourPointer();
updateTimePointer();
}
private void initBound() {
radius = getWidth() * 3 / 7;
radiusScaleRing = radius * 4 / 3;
centerX = getWidth() / 2;
centerY = getHeight() / 2;
boundTimeRing = new RectF(centerX - radius, centerY - radius, centerX + radius, centerY + radius);
boundScaleRing = new RectF(centerX - radiusScaleRing, centerY - radiusScaleRing, centerX + radiusScaleRing, centerY + radiusScaleRing);
hpPointerLength = radius * 3 / 5;
mpPointerLength = radius * 3 / 5;
}
private void initScaleRing() {
// Scale ring paint
paintScaleRing = new Paint();
paintScaleRing.setAntiAlias(true);
paintScaleRing.setStyle(Paint.Style.STROKE);
paintScaleRing.setStrokeWidth(strokeWidthScaleRing);
paintScaleRing.setColor(colorScaleRing);
// Number text paint
paintNumber = new Paint();
paintNumber.setAntiAlias(true);
paintNumber.setStyle(Paint.Style.FILL);
paintNumber.setColor(colorScaleRing);
paintNumber.setTextSize(scaleTextSize);
// Parse text baseline
float scaleTextWidthTwo = paintNumber.measureText("12");
float scaleTextWidthOne = paintNumber.measureText("6");
float scaleTextHeight = paintNumber.measureText("12");
RectF topTextBound = new RectF();
topTextBound.left = centerX - scaleTextWidthTwo / 2;
topTextBound.top = centerY - radiusScaleRing - scaleTextHeight / 2;
topTextBound.right = centerX + scaleTextWidthTwo / 2;
topTextBound.bottom = centerY - radiusScaleRing + scaleTextHeight / 2;
RectF bottomTextBound = new RectF();
bottomTextBound.left = centerX - scaleTextWidthOne / 2;
bottomTextBound.top = centerY + radiusScaleRing - scaleTextHeight / 2;
bottomTextBound.right = centerX + scaleTextWidthOne / 2;
bottomTextBound.bottom = centerY + radiusScaleRing + scaleTextHeight / 2;
RectF leftTextBound = new RectF();
leftTextBound.left = centerX - radiusScaleRing - scaleTextWidthOne / 2;
leftTextBound.top = centerY - scaleTextHeight / 2;
leftTextBound.right = centerX - radiusScaleRing + scaleTextWidthOne / 2;
leftTextBound.bottom = centerY + scaleTextHeight / 2;
RectF rightTextBound = new RectF();
rightTextBound.left = centerX + radiusScaleRing - scaleTextWidthOne / 2;
rightTextBound.top = leftTextBound.top;
rightTextBound.right = centerX + radiusScaleRing + scaleTextWidthOne / 2;
rightTextBound.bottom = leftTextBound.bottom;
Paint.FontMetrics fm = paintNumber.getFontMetrics();
scaleTextDrawCoordinates = new float[4][2];
scaleTextDrawCoordinates[0][0] = topTextBound.left;
scaleTextDrawCoordinates[0][1] = topTextBound.top + (topTextBound.bottom - topTextBound.top) / 2 - (fm.bottom - fm.top) / 2 - fm.top;
scaleTextDrawCoordinates[1][0] = bottomTextBound.left;
scaleTextDrawCoordinates[1][1] = bottomTextBound.top + (bottomTextBound.bottom - bottomTextBound.top) / 2 - (fm.bottom - fm.top) / 2 - fm
.top;
scaleTextDrawCoordinates[2][0] = leftTextBound.left;
scaleTextDrawCoordinates[2][1] = leftTextBound.top + (leftTextBound.bottom - leftTextBound.top) / 2 - (fm.bottom - fm.top) / 2 - fm.top;
scaleTextDrawCoordinates[3][0] = rightTextBound.left;
scaleTextDrawCoordinates[3][1] = rightTextBound.top + (rightTextBound.bottom - rightTextBound.top) / 2 - (fm.bottom - fm.top) / 2 - fm.top;
}
private void initHourPointer() {
// Center Ring
paintHPOuterRing = new Paint();
paintHPOuterRing.setStyle(Paint.Style.FILL);
paintHPOuterRing.setAntiAlias(true);
paintHPOuterRing.setColor(colorHPOuterRing);
paintHPInnerCircle = new Paint(paintHPOuterRing);
paintHPInnerCircle.setColor(colorBg);
// Minute Pointer
paintHourPointer = new Paint();
paintHourPointer.setAntiAlias(true);
paintHourPointer.setStyle(Paint.Style.FILL);
paintHourPointer.setColor(colorHourPointer);
float topX1 = centerX - hpTopEdgeLength / 2;
float topY1 = centerY - hpPointerLength + strokeWidthRing / 2;
float topX2 = centerX + hpTopEdgeLength / 2;
float topY2 = topY1;
float topCX1 = centerX;
float topCY1 = topY1 - hpTopCYOffset;
float bottomX1 = centerX - hpBottomEdgeLength / 2;
float bottomY1 = centerY;
float bottomX2 = centerX + hpBottomEdgeLength / 2;
float bottomY2 = bottomY1;
pathHourPointer = new Path();
pathHourPointer.moveTo(bottomX1, bottomY1);
pathHourPointer.lineTo(bottomX2, bottomY2);
pathHourPointer.lineTo(topX2, topY2);
pathHourPointer.quadTo(topCX1, topCY1, topX1, topY1);
pathHourPointer.close();
}
private void initMinutePointer() {
// Center Ring
paintMPOuterRing = new Paint();
paintMPOuterRing.setStyle(Paint.Style.FILL);
paintMPOuterRing.setAntiAlias(true);
paintMPOuterRing.setColor(colorMPOuterRing);
paintMPInnerCircle = new Paint(paintMPOuterRing);
paintMPInnerCircle.setColor(colorBg);
// Minute Pointer
paintMinutePointer = new Paint();
paintMinutePointer.setStyle(Paint.Style.FILL);
paintMinutePointer.setAntiAlias(true);
paintMinutePointer.setColor(colorMinutePointer);
float topX1 = centerX - mpTopEdgeLength / 2;
float topY1 = centerY - mpPointerLength + strokeWidthRing / 2;
float topX2 = centerX + mpTopEdgeLength / 2;
float topY2 = topY1;
float topCX1 = centerX;
float topCY1 = topY1 - mpTopCYOffset;
float bottomX1 = centerX - mpBottomEdgeLength / 2;
float bottomY1 = centerY;
float bottomX2 = centerX + mpBottomEdgeLength / 2;
float bottomY2 = bottomY1;
pathMinutePointer = new Path();
pathMinutePointer.moveTo(bottomX1, bottomY1);
pathMinutePointer.lineTo(bottomX2, bottomY2);
pathMinutePointer.lineTo(topX2, topY2);
pathMinutePointer.quadTo(topCX1, topCY1, topX1, topY1);
pathMinutePointer.close();
}
private void initTriangleSecondPointer() {
paintTriangle = new Paint();
paintTriangle.setColor(colorTriangle);
paintTriangle.setStyle(Paint.Style.FILL);
paintTriangle.setAntiAlias(true);
float height = (float) (1.0 * Math.sqrt(3f) / 2 * triangleSideLength);
float point1x = centerX;
float point1y = centerY - radius + strokeWidthRing / 2 + trianglePointerOffset;
float point2x = point1x - triangleSideLength / 2;
float point2y = point1y + height;
float point3x = point1x + triangleSideLength / 2;
float point3y = point1y + height;
pathTriangle = new Path();
pathTriangle.moveTo(point1x, point1y);
pathTriangle.lineTo(point2x, point2y);
pathTriangle.lineTo(point3x, point3y);
pathTriangle.close();
}
private void initBgRing() {
paintBgRing = new Paint();
paintBgRing.setStyle(Paint.Style.STROKE);
paintBgRing.setStrokeWidth(strokeWidthRing);
paintBgRing.setAntiAlias(true);
paintBgRing.setPathEffect(new DashPathEffect(ringDashIntervals, 0));
paintBgRing.setColor(colorBgRing);
}
private void initProgressRing() {
paintProgressRing = new Paint(paintBgRing);
paintProgressRing.setColor(Color.WHITE);
sweepGradient = new SweepGradient(centerX, centerY, sweepGradientColors, sweepGradientColorPos);
paintProgressRing.setShader(sweepGradient);
}
private void startNewSecondAnim() {
if (DEBUG) {
Log.d(TAG, "startNewSecondAnim");
}
cancelSecondAnimIfNeed();
updateTimePointer();
final float startDegree = 0f;
final float endDegree = 360f;
secondAnim = ValueAnimator.ofFloat(startDegree, endDegree);
// FIXME 不知为何某些机型动画实际执行时间是duration的一半
// 在构造函数里启动动画就正常,在其他方法(如onAttachedToWindow或onSizeChanged)里调用本方法就不正常
// 出问题的机型:小米4、小米1S
secondAnim.setDuration(60 * 1000);
secondAnim.setInterpolator(new LinearInterpolator());
secondAnim.setRepeatMode(ValueAnimator.RESTART);
secondAnim.setRepeatCount(ValueAnimator.INFINITE);
secondAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
private float lastDrawValue = 0;
private float drawInterval = 0.1f;
private float lastUpdatePointerValue = 0;
private float updatePointerInterval = 360 / 60 * 5;
@Override
public void onAnimationUpdate(ValueAnimator animation) {
float newValue = (float) animation.getAnimatedValue();
// Check if it is the time to update pointer position
float increasedPointerValue = newValue - lastUpdatePointerValue;
if (increasedPointerValue < 0) {
increasedPointerValue = endDegree + increasedPointerValue;
}
if (increasedPointerValue >= updatePointerInterval) {
lastUpdatePointerValue = newValue;
updateTimePointer();
}
// Check if it is the time to invalidate
float increasedDrawValue = newValue - lastDrawValue;
if (increasedDrawValue < 0) {
increasedDrawValue = endDegree + increasedDrawValue;
}
if (increasedDrawValue >= drawInterval) {
lastDrawValue = newValue;
rotateSecondPointer += increasedDrawValue;
invalidate();
// if (DEBUG) {
// Log.d(TAG, String.format("newValue:%s , currentPlayTime:%s", newValue, animation.getCurrentPlayTime()));
// }
}
}
});
secondAnim.start();
}
private void cancelSecondAnimIfNeed() {
if (secondAnim != null && (secondAnim.isStarted() || secondAnim.isRunning())) {
secondAnim.cancel();
if (DEBUG) {
Log.d(TAG, "cancelSecondAnimIfNeed");
}
}
}
private void startNewSteadyAnim() {
final String propertyNameRotateX = "canvasRotateX";
final String propertyNameRotateY = "canvasRotateY";
PropertyValuesHolder holderRotateX = PropertyValuesHolder.ofFloat(propertyNameRotateX, canvasRotateX, 0);
PropertyValuesHolder holderRotateY = PropertyValuesHolder.ofFloat(propertyNameRotateY, canvasRotateY, 0);
steadyAnim = ValueAnimator.ofPropertyValuesHolder(holderRotateX, holderRotateY);
steadyAnim.setDuration(1000);
steadyAnim.setInterpolator(new BounceInterpolator());
steadyAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
canvasRotateX = (float) animation.getAnimatedValue(propertyNameRotateX);
canvasRotateY = (float) animation.getAnimatedValue(propertyNameRotateY);
}
});
steadyAnim.start();
}
private void cancelSteadyAnimIfNeed() {
if (steadyAnim != null && (steadyAnim.isStarted() || steadyAnim.isRunning())) {
steadyAnim.cancel();
}
}
private void updateTimePointer() {
int second = Calendar.getInstance().get(Calendar.SECOND);
int minute = Calendar.getInstance().get(Calendar.MINUTE);
int hour = Calendar.getInstance().get(Calendar.HOUR);
float percentSecond = (float) (1.0 * second / 60);
float percentMinute = (float) (1.0 * (minute * 60 + second) / (60 * 60));
float percentHour = (float) (1.0 * (hour * 60 * 60 + minute * 60 + second) / (60 * 60 * 12));
rotateSecondPointer = 360 * percentSecond;
rotateMinutePointer = 360 * percentMinute;
rotateHourPointer = 360 * percentHour;
}
private float getProgressRingRotateDegree() {
float degree = (rotateSecondPointer + progressRingInitRotateDegree) % 360;
return degree;
}
public float dp2px(float dpValue) {
return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dpValue, getResources().getDisplayMetrics());
}
public float sp2px(float spValue) {
return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, spValue, getResources().getDisplayMetrics());
}
private BroadcastReceiver receiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
if (intent == null) {
return;
}
String action = intent.getAction();
if (TextUtils.isEmpty(action)) {
return;
}
if (DEBUG) {
Log.d(TAG, String.format("action -> %s", action));
}
if (action.equals(Intent.ACTION_TIME_TICK)) {
updateTimePointer();
} else if (action.equals(Intent.ACTION_SCREEN_ON)) {
startNewSecondAnim();
} else if (action.equals(Intent.ACTION_SCREEN_OFF)) {
cancelSecondAnimIfNeed();
}
}
};
}